Zephyr 自定义 BSP
Zephyr 提供了自定义 board 的功能,可以通过 Device Tree 的方式配置外设,与 Linux 相似。本文介绍如何在 $ZEPHYR_BASE 目录之外新建项目。
本文的项目是在 Zephyr 项目配置和调试 的基础上进行的。另外,阅读本文之前建议查阅 Zephyr 工作逻辑。
项目结构
首先,新建 boards/<vendor> 文件夹。笔者手中板子的 <vendor> 供应商为 st,故设置目录为 boards/st。假设我们的板子名为 my_board,则在该目录下新建以下文件:
my_board.dts:这就是设备树文件,描述了其外设的配置信息。Kconfig.my_board:板子有关 SoC 的配置文件。my_board_defconfig:有关 Zephyr 的对于该板子的配置裁剪文件。board.yml:板子描述信息,west根据这个文件检索板子。board.cmake:描述了编译、烧录等功能的cmake文件。support/:该目录为openocd等工具的配置文件目录,用于调试。
各文件说明
Kconfig.my_board
内容如下:
config BOARD_MY_BOARD
select SOC_STM32F411XE
其指明了这块板子使用的 SOC 型号。之后,Zephyr 会在 soc 中寻找该芯片的配置文件等。本处为 STM32F411XE。
my_board_defconfig
内容如下:
# SPDX-License-Identifier: Apache-2.0
# Enable MPU
CONFIG_ARM_MPU=y
# Enable HW stack protection
CONFIG_HW_STACK_PROTECTION=y
CONFIG_SERIAL=y
# console
CONFIG_CONSOLE=y
CONFIG_UART_CONSOLE=y
# enable GPIO
CONFIG_GPIO=y
其规定了某些 Zephyr 功能的开关情况,比如 CONFIG_GPIO 指定了是否使用 GPIO 等。
board.yml
内容如下:
board:
name: my_board
full_name: ST Nucleo F411RE Copy
vendor: st
socs:
- name: stm32f411xe
其描述了板子的基本信息,包括板子名称、可读的名称、供应商和 SoC。west 根据这个文件索引板子列表。
board.cmake
内容如下:
# keep first
board_runner_args(stm32cubeprogrammer "--port=swd" "--reset-mode=hw")
board_runner_args(jlink "--device=STM32F411RE" "--speed=4000")
# keep first
include(${ZEPHYR_BASE}/boards/common/stm32cubeprogrammer.board.cmake)
include(${ZEPHYR_BASE}/boards/common/openocd-stm32.board.cmake)
include(${ZEPHYR_BASE}/boards/common/jlink.board.cmake)
include(${ZEPHYR_BASE}/boards/common/stlink_gdbserver.board.cmake)
可以看到,其描述了烧录用的工具等。有了这个文件的描述,就可以进行 west flash 等操作。
support/openocd.cfg
该目录下暂时只有一个文件:openocd.cfg。他描述了 OpenOCD 的一些行为。内容如下:
source [find board/st_nucleo_f4.cfg]
$_TARGETNAME configure -event gdb-attach {
echo "Debugger attaching: halting execution"
reset halt
gdb_breakpoint_override hard
}
$_TARGETNAME configure -event gdb-detach {
echo "Debugger detaching: resuming execution"
resume
}
有了这个文件,结合 board.cmake,就可以进行 west debug、west debugserver 等操作。
my_board.dts
这是描述外设的设备树文件,内容如下:
/dts-v1/;
#include <st/f4/stm32f411Xe.dtsi>
#include <st/f4/stm32f411r(c-e)tx-pinctrl.dtsi>
#include <zephyr/dt-bindings/input/input-event-codes.h>
/ {
model = "STMicroelectronics STM32F411RE-NUCLEO board";
compatible = "st,stm32f411re-nucleo";
chosen {
zephyr,console = &usart2;
zephyr,shell-uart = &usart2;
zephyr,sram = &sram0;
zephyr,flash = &flash0;
};
leds: leds {
compatible = "gpio-leds";
green_led_2: led_2 {
gpios = <&gpioa 5 GPIO_ACTIVE_HIGH>;
label = "User LD2";
};
};
gpio_keys {
compatible = "gpio-keys";
user_button: button {
label = "User";
gpios = <&gpioc 13 GPIO_ACTIVE_LOW>;
zephyr,code = <INPUT_KEY_0>;
};
};
aliases {
led0 = &green_led_2;
sw0 = &user_button;
};
};
&clk_lsi {
status = "okay";
};
&clk_hse {
hse-bypass;
clock-frequency = <DT_FREQ_M(8)>; /* STLink 8MHz clock */
status = "okay";
};
&pll {
div-m = <8>;
mul-n = <384>;
div-p = <4>;
div-q = <8>;
clocks = <&clk_hse>;
status = "okay";
};
&rcc {
clocks = <&pll>;
clock-frequency = <DT_FREQ_M(96)>;
ahb-prescaler = <1>;
apb1-prescaler = <2>;
apb2-prescaler = <1>;
};
&usart2 {
pinctrl-0 = <&usart2_tx_pa2 &usart2_rx_pa3>;
pinctrl-names = "default";
current-speed = <115200>;
status = "okay";
};
具体的解析详见 Zephyr 编写设备树文件。
引用 BSP
由于该 BSP 的位置不在 $ZEPHYR_BASE 内,所以需要在 CMakeLists 中添加内容:
list(APPEND BOARD_ROOT ${CMAKE_SOURCE_DIR})
set(BOARD my_board)
这样,west 就可以将当前目录添加到 BOARD_ROOT 目录中,我们的 BSP 就可以被搜索到。运行 west build 编译。
上面的 BSP 中包含了串口、LED 和按键的外设配置。所以可以在 main.c 中调用以测试:
#include <stdio.h>
#include <zephyr/kernel.h>
#include "zephyr/drivers/gpio.h"
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(DT_ALIAS(led0), gpios);
static const struct gpio_dt_spec button =
GPIO_DT_SPEC_GET(DT_ALIAS(sw0), gpios);
int main() {
int ret;
if (!gpio_is_ready_dt(&led) || !gpio_is_ready_dt(&button)) {
return 0;
}
ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);
if (ret < 0) return 0;
ret = gpio_pin_configure_dt(&button, GPIO_INPUT);
if (ret < 0) return 0;
while (1) {
int val = gpio_pin_get_dt(&button);
if (val > 0) {
gpio_pin_toggle_dt(&led);
k_sleep(K_MSEC(100));
} else {
gpio_pin_toggle_dt(&led);
k_sleep(K_MSEC(1000));
}
printk("LED STATE: %d\n", val);
}
return 0;
}
可以观察到,LED 成功亮起,受按钮控制。
资源
本文项目链接:GitHub - heptarri